home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue43 / delay / HVDll.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1999-01-28  |  11.9 KB  |  458 lines

  1. unit HVDll;
  2. //
  3. // Support for DelayLoading of DLLs ß la VC++6.0
  4. // Written by Hallvard Vassbotn (hallvard@falcon.no), January 1999
  5. //
  6. interface
  7.  
  8. uses
  9.   Windows,
  10.   Classes,
  11.   SysUtils,
  12.   HVHeaps;
  13.  
  14. type
  15.   // Structures to keep the address of function variables and name/id pairs
  16.   PPointer = ^pointer;
  17.   PEntry = ^TEntry;
  18.   TEntry = packed record
  19.     Proc: PPointer;
  20.     case integer of
  21.       0 : (Name: PChar);
  22.       1 : (ID  : Longint);
  23.     end;
  24.   PEntries = ^TEntries;
  25.   TEntries = packed array[0..High(Word)-1] of TEntry;
  26.  
  27.   // Structures to generate the per-routine thunks
  28.   PThunk = ^TThunk;
  29.   TThunk = packed record
  30.     CALL  : byte;
  31.     OFFSET: integer;
  32.   end;
  33.   PThunks = ^TThunks;
  34.   TThunks = packed array[0..High(Word)-1] of TThunk;
  35.  
  36.   // Structure to generate the per-DLL thunks
  37.   TThunkHeader = packed record
  38.     PUSH   : byte;
  39.     VALUE  : pointer;
  40.     JMP    : byte;
  41.     OFFSET : integer;
  42.   end;
  43.  
  44.   // The combined per-DLL and per-routine thunks
  45.   PThunkingCode = ^TThunkingCode;
  46.   TThunkingCode = packed record
  47.     ThunkHeader : TThunkHeader;
  48.     Thunks      : TThunks;
  49.   end;
  50.  
  51.   // The base class that provides DelayLoad capability
  52.   TDll = class(TObject)
  53.   private
  54.     FEntries  : PEntries;
  55.     FThunkingCode: PThunkingCode;
  56.     FCount    : integer;
  57.     FFullPath : string;
  58.     FHandle   : HMODULE;
  59.     function GetHandle: HMODULE;
  60.     procedure SetFullPath(const Value: string);
  61.     function GetProcs(Index: integer): pointer;
  62.     procedure SetProcs(Index: integer; Value: pointer);
  63.     function GetAvailable: boolean;
  64.     function GetLoaded: boolean;
  65.     function LoadProcAddrFromIndex(Index: integer; var Addr: pointer): boolean;
  66.     procedure ActivateThunks;
  67.     function GetEntryName(Index: integer): string;
  68.   protected
  69.     function LoadHandle: HMODULE; virtual;
  70.     class procedure Error(const Msg: string; Args: array of const);
  71.     procedure CreateThunks;
  72.     procedure DestroyThunks;
  73.     function HasThunk(Thunk: PThunk): boolean;
  74.     function GetProcAddrFromIndex(Index: integer): pointer;
  75.     function DelayLoadFromThunk(Thunk: PThunk): pointer; register;
  76.     function DelayLoadIndex(Index: integer): pointer;
  77.     function GetIndexFromThunk(Thunk: PThunk): integer;
  78.     function GetIndexFromProc(Proc: PPointer): integer;
  79.     function ValidIndex(Index: integer): boolean;
  80.     procedure CheckIndex(Index: integer);
  81.     property Procs[Index: integer]: pointer read GetProcs write SetProcs;
  82.   public
  83.     constructor Create(const DllName: string; const Entries: array of TEntry);
  84.     destructor Destroy; override;
  85.     procedure Load;
  86.     procedure Unload;
  87.     function HasRoutine(Proc: PPointer): boolean;
  88.     function HookRoutine(Proc: PPointer; HookProc: Pointer; var OrgProc{: Pointer}): boolean;
  89.     function UnHookRoutine(Proc: PPointer; var OrgProc{: Pointer}): boolean;
  90.     property FullPath: string read FFullPath write SetFullPath;
  91.     property Handle: HMODULE read GetHandle;
  92.     property Loaded: boolean read GetLoaded;
  93.     property Available: boolean read GetAvailable;
  94.     property Count: integer read FCount;
  95.     property EntryName[Index: integer]: string read GetEntryName;
  96.   end;
  97.  
  98.   // The class that keeps a list of all created TDll instances in one place
  99.   TDllNotifyAction = (daLoadedDll, daUnloadedDll, daLinkedRoutine);
  100.   TDllNotifyEvent = procedure(Sender: TDll; Action: TDllNotifyAction; Index: integer) of object;
  101.   TDlls = class(TList)
  102.   private
  103.     FCodeHeap: TCodeHeap;
  104.     FOnDllNotify: TDllNotifyEvent;
  105.     function GetDlls(Index: integer): TDll;
  106.   protected
  107.     procedure DllNotify(Sender: TDll; Action: TDllNotifyAction; Index: integer);
  108.     property CodeHeap: TCodeHeap read FCodeHeap;
  109.   public
  110.     constructor Create;
  111.     destructor Destroy; override;
  112.     property Dlls[Index: integer]: TDll read GetDlls; default;
  113.     property OnDllNotify: TDllNotifyEvent read FOnDllNotify write FOnDllNotify;
  114.   end;
  115.  
  116.   EDllError = class(Exception);
  117.  
  118. var
  119.   Dlls: TDlls;
  120.  
  121. implementation
  122.  
  123. {$IFDEF VER90}
  124. const
  125. {$ELSE}
  126. resourcestring
  127. {$ENDIF}
  128.   SIndexOutOfRange      = 'DLL-entry index out of range (%d)';
  129.   SOrdinal              = 'ordinal #';
  130.   SCannotLoadLibrary    = 'Could not find the library: "%s"'#13#10'(%s)';
  131.   SCannotGetProcAddress = 'Could not find the routine "%s" in the library "%s"'#13#10'(%s)';
  132.   SCannotFindThunk      = 'Could not find the TDll object corresponding to the thunk address %p';
  133.  
  134. { Helper routines }
  135.  
  136. function EntryToString(const Entry: TEntry): string;
  137. begin
  138.   if Hi(Entry.ID) <> 0
  139.   then Result := string(Entry.Name)
  140.   else Result := SOrdinal+IntToStr(Entry.ID);
  141. end;
  142.  
  143. procedure ThunkingTarget;
  144. const
  145.   TThunkSize = SizeOf(TThunk);
  146. asm
  147.   // Save register-based parameters
  148.   PUSH    EAX
  149.   PUSH    EDX
  150.   PUSH    ECX
  151. { Stack layout at this point:
  152.   24 [Stack based parameters]
  153.   20 [User code RetAdr]
  154.   16 [Thunk Ret-Adr]
  155.   12 [Self]
  156.    8 [EAX]
  157.    4 [EDX]
  158.    0 [ECX] <-ESP}
  159.   // Get the caller's return address (i.e. one of the thunks)
  160.   MOV     EAX, [ESP+12]   // Self
  161.   MOV     EDX, [ESP+16]   // Thunk
  162.   // The return address is just after the thunk that
  163.   // called us, so go back one step
  164.   SUB     EDX, TYPE TThunk // Using SizeOf(TThunk) here does not work. BASM bug?
  165.   // Do the rest in Pascal
  166.   CALL    TDll.DelayLoadFromThunk{(Self, Thunk);}
  167.   // Now patch the return address on the stack so that we "return" to the DLL routine
  168.   MOV     [ESP+16], EAX
  169.   // Restore register-based parameters
  170.   POP     ECX
  171.   POP     EDX
  172.   POP     EAX
  173.   // Remove the Self pointer!
  174.   ADD        ESP,  4
  175.   // "RETurn" to the DLL!
  176. end;
  177.  
  178. { TDll }
  179.  
  180. constructor TDll.Create(const DllName: string; const Entries: array of TEntry);
  181. begin
  182.   inherited Create;
  183.   FFullPath := DllName;
  184.   FEntries  := @Entries;
  185.   FCount    := High(Entries) - Low(Entries) + 1;
  186.   CreateThunks;
  187.   ActivateThunks;
  188.   Dlls.Add(Self);
  189. end;
  190.  
  191. destructor TDll.Destroy;
  192. begin
  193.   Dlls.Remove(Self);
  194.   Unload;
  195.   DestroyThunks;
  196.   inherited Destroy;
  197. end;
  198.  
  199. procedure TDll.CreateThunks;
  200. const
  201.   CallInstruction = $E8;
  202.   PushInstruction = $68;
  203.   JumpInstruction = $E9;
  204. var
  205.   i : integer;
  206. begin
  207.   // Get a memory block large enough for the thunks
  208.   Dlls.CodeHeap.GetMem(FThunkingCode, SizeOf(TThunkHeader) + SizeOf(TThunk) * Count);
  209.  
  210.   // Generate some machine code in the thunks
  211.   with FThunkingCode^, ThunkHeader do
  212.   begin
  213.     // The per-Dll thunk does this:
  214.     // PUSH    Self
  215.     // JMP     ThunkingTarget
  216.     PUSH   := PushInstruction;
  217.     VALUE  := Self;
  218.     JMP    := JumpInstruction;
  219.     OFFSET := PChar(@ThunkingTarget) - PChar(@Thunks[0]);
  220.     for i := 0 to Count-1 do
  221.       with Thunks[i] do
  222.       begin
  223.         // The per-entry thunk does this:
  224.         // CALL @ThunkingCode^.ThunkHeader
  225.         CALL   := CallInstruction;
  226.         OFFSET := PChar(@FThunkingCode^.ThunkHeader) - PChar(@Thunks[i+1]);
  227.       end;
  228.   end;
  229. end;
  230.  
  231. procedure TDll.DestroyThunks;
  232. begin
  233.   if Assigned(FThunkingCode) then
  234.   begin
  235.     Dlls.CodeHeap.FreeMem(FThunkingCode);
  236.     FThunkingCode := nil;
  237.   end;
  238. end;
  239.  
  240. function TDll.DelayLoadFromThunk(Thunk: PThunk): pointer; register;
  241. begin
  242.   Result := DelayLoadIndex(GetIndexFromThunk(Thunk));
  243. end;
  244.  
  245. function TDll.DelayLoadIndex(Index: integer): pointer;
  246. begin
  247.   Result := GetProcAddrFromIndex(Index);
  248.   FEntries^[Index].Proc^ := Result;
  249. end;
  250.  
  251. class procedure TDll.Error(const Msg: string; Args: array of const);
  252. begin
  253.   raise EDllError.CreateFmt(Msg, Args);
  254. end;
  255.  
  256. function TDll.LoadHandle: HMODULE;
  257. begin
  258.   if FHandle = 0 then
  259.   begin
  260.     FHandle := Windows.LoadLibrary(PChar(FullPath));
  261.     if FHandle <> 0 then
  262.       Dlls.DllNotify(Self, daLoadedDll, -1);
  263.   end;
  264.   Result := FHandle;
  265. end;
  266.  
  267. function TDll.GetHandle: HMODULE;
  268. begin
  269.   Result := FHandle;
  270.   if Result = 0 then
  271.   begin
  272.     Result := LoadHandle;
  273.     if Result = 0 then
  274.       Error(SCannotLoadLibrary, [FullPath, SysErrorMessage(GetLastError)]);
  275.   end;
  276. end;
  277.  
  278. function TDll.GetIndexFromThunk(Thunk: PThunk): integer;
  279. begin
  280.   // We calculate the thunk index by subtracting the start of the array
  281.   // and dividing by the size of the array elements
  282.   Result := (PChar(Thunk) - PChar(@FThunkingCode^.Thunks[0])) div SizeOf(TThunk);
  283. end;
  284.  
  285. function TDll.LoadProcAddrFromIndex(Index: integer; var Addr: pointer): boolean;
  286. begin
  287.   Result := ValidIndex(Index);
  288.   if Result then
  289.   begin
  290.     Addr := Windows.GetProcAddress(Handle, FEntries^[Index].Name);
  291.     Result := Assigned(Addr);
  292.     if Result then
  293.       Dlls.DllNotify(Self, daLinkedRoutine, Index);
  294.   end;
  295. end;
  296.  
  297. function TDll.GetProcAddrFromIndex(Index: integer): pointer;
  298. begin
  299.   if not LoadProcAddrFromIndex(Index, Result) then
  300.     Error(SCannotGetProcAddress, [EntryName[Index], FullPath, SysErrorMessage(GetLastError)]);
  301. end;
  302.  
  303. function TDll.HasThunk(Thunk: PThunk): boolean;
  304. begin
  305.   // The thunk belongs to us if its address is in the thunk array
  306.   Result := (PChar(Thunk) >= PChar(@FThunkingCode^.Thunks[0])) and
  307.             (PChar(Thunk) <= PChar(@FThunkingCode^.Thunks[Count-1]));
  308. end;
  309.  
  310. procedure TDll.Load;
  311. var
  312.   i : integer;
  313. begin
  314.   for i := 0 to Count-1 do
  315.     DelayLoadIndex(i);
  316. end;
  317.  
  318. procedure TDll.SetFullPath(const Value: string);
  319. begin
  320.   if CompareText(FFullPath, Value) <> 0 then
  321.   begin
  322.     Unload;
  323.     FFullPath := Value;
  324.   end;
  325. end;
  326.  
  327. function TDll.GetEntryName(Index: integer): string;
  328. begin
  329.   if ValidIndex(Index)
  330.   then Result := EntryToString(FEntries^[Index])
  331.   else Result := Format(SIndexOutOfRange, [Index]);
  332. end;
  333.  
  334. procedure TDll.ActivateThunks;
  335. // Patch the procedure variables to point to the generated thunks
  336. var
  337.   i : integer;
  338. begin
  339.   for i := 0 to Count-1 do
  340.     FEntries^[i].Proc^ := @FThunkingCode^.Thunks[i];
  341. end;
  342.  
  343. procedure TDll.Unload;
  344. begin
  345.   ActivateThunks;
  346.   if FHandle <> 0 then
  347.   begin
  348.     FreeLibrary(FHandle);
  349.     Dlls.DllNotify(Self, daUnloadedDll, -1);
  350.     FHandle := 0;
  351.   end;
  352. end;
  353.  
  354. function TDll.ValidIndex(Index: integer): boolean;
  355. begin
  356.   Result := (Index >= 0) and (Index <= Count-1);
  357. end;
  358.  
  359. procedure TDll.CheckIndex(Index: integer);
  360. begin
  361.   if not ValidIndex(Index) then
  362.     Error(SIndexOutOfRange, [Index]);
  363. end;
  364.  
  365. function TDll.GetProcs(Index: integer): pointer;
  366. begin
  367.   CheckIndex(Index);
  368.   Result := FEntries^[Index].Proc^;
  369. end;
  370.  
  371. procedure TDll.SetProcs(Index: integer; Value: pointer);
  372. begin
  373.   CheckIndex(Index);
  374.   FEntries^[Index].Proc^ := Value;
  375. end;
  376.  
  377. function TDll.GetAvailable: boolean;
  378. begin
  379.   Result := (LoadHandle <> 0);
  380. end;
  381.  
  382. function TDll.GetLoaded: boolean;
  383. begin
  384.   Result := (FHandle <> 0);
  385. end;
  386.  
  387. function TDll.GetIndexFromProc(Proc: PPointer): integer;
  388. begin
  389.   for Result := 0 to Count-1 do
  390.     if FEntries^[Result].Proc = Proc then
  391.       Exit;
  392.   Result := -1;
  393. end;
  394.  
  395. function TDll.HasRoutine(Proc: PPointer): boolean;
  396. begin
  397.   Result := Available and
  398.             ((not HasThunk(Proc^)) or
  399.               LoadProcAddrFromIndex(GetIndexFromProc(Proc), Proc^));
  400. end;
  401.  
  402. function TDll.HookRoutine(Proc: PPointer; HookProc: Pointer; var OrgProc{: Pointer}): boolean;
  403. begin
  404.   Result := HasRoutine(Proc);
  405.   if Result then
  406.   begin
  407.     Pointer(OrgProc) := Proc^;
  408.     Proc^   := HookProc;
  409.   end;
  410. end;
  411.  
  412. function TDll.UnHookRoutine(Proc: PPointer; var OrgProc{: Pointer}): boolean;
  413. begin
  414.   Result := Assigned(Pointer(OrgProc));
  415.   if Result then
  416.   begin
  417.     Proc^ := Pointer(OrgProc);
  418.     Pointer(OrgProc) := nil;
  419.   end;
  420. end;
  421.  
  422. { TDlls }
  423.  
  424. constructor TDlls.Create;
  425. begin
  426.   inherited Create;
  427.   FCodeHeap := TCodeHeap.Create;
  428. end;
  429.  
  430. destructor TDlls.Destroy;
  431. var
  432.   i : integer;
  433. begin
  434.   for i := Count-1 downto 0 do
  435.     Dlls[i].Free;
  436.   FCodeHeap.Free;
  437.   FCodeHeap := nil;
  438.   inherited Destroy;
  439. end;
  440.  
  441. procedure TDlls.DllNotify(Sender: TDll; Action: TDllNotifyAction; Index: integer);
  442. begin
  443.   if Assigned(FOnDllNotify) then
  444.     FOnDllNotify(Sender, Action, Index);
  445. end;
  446.  
  447. function TDlls.GetDlls(Index: integer): TDll;
  448. begin
  449.   Result := TDll(Items[Index]);
  450. end;
  451.  
  452. initialization
  453.   Dlls := TDlls.Create;
  454. finalization
  455.   Dlls.Free;
  456.   Dlls := nil;
  457. end.
  458.